using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Diagnostics;


namespace Dragoon
{
  public delegate void MatrixDelegate(ref Matrix m);

  public interface ICamera
  {
    Matrix ViewMatrix { get; }
    Vector3 Position { get; }
    Matrix ProjectionMatrix { get; }
    Vector3 LightDirection { get; }
  }

  public class EffectComponent : Microsoft.Xna.Framework.DrawableGameComponent
  {
    private readonly string _effectName = "effects";
    protected EffectComponent(Game g) : base(g) { }
    protected EffectComponent(Game g, string fxName) : base(g) { _effectName = fxName; }
    protected Effect _effect;

    public override void Initialize()
    {
      _effect = Game.Content.Load<Effect>(_effectName);
      base.Initialize();
    }
  }
  public class CameraBase : EffectComponent, ICamera
  {
    private Matrix _viewMatrix;
    private Vector3 _lightDirection;

    public Vector3 LightDirection
    {
      get { return _lightDirection; }
    }

    Matrix projectionMatrix;
    protected Vector3 _position = new Vector3(80, 30, -50);


    public Matrix ViewMatrix
    {
      get
      {
        return _viewMatrix;
      }
      protected set
      {
        _viewMatrix = value;
        if (OnViewUpdate != null)
          OnViewUpdate(ref _viewMatrix);
      }
    }


    public event MatrixDelegate OnViewUpdate;

    public Matrix TranslationMatrix = Matrix.Identity;
    protected Matrix WorldMatrix = Matrix.Identity;

    public CameraBase(Game game)
      : base(game)
    {
      _lightDirection = new Vector3(-0.5f, -1, -0.5f);
      _lightDirection.Normalize();
    }

    public Vector3 Position
    {
      get { return _position; }
    }

    public Matrix ProjectionMatrix
    {
      get { return projectionMatrix; }
    }

    protected override void LoadContent()
    {
      _viewMatrix = Matrix.CreateLookAt(new Vector3(130, 30, -50), new Vector3(0, 0, -40), new Vector3(0, 1, 0));
      projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.3f, 1000.0f);
      base.LoadContent();
    }


    public override void Draw(GameTime gameTime)
    {
      GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);

      _effect.Parameters["xEnableLighting"].SetValue(true);
      _effect.Parameters["xAmbient"].SetValue(0.3f);
      _effect.Parameters["xLightDirection"].SetValue(_lightDirection);

      _effect.Parameters["xView"].SetValue(_viewMatrix);
      _effect.Parameters["xProjection"].SetValue(projectionMatrix);
      _effect.Parameters["xWorld"].SetValue(WorldMatrix);

      base.Draw(gameTime);
    }
  }

  public class CameraNormal : CameraBase
  {

    protected float _leftrightRot = MathHelper.PiOver2;
    protected float _updownRot = -MathHelper.Pi / 10.0f;
    private Vector3 cameraFinalTarget;
    Matrix cameraRotation;

    public Matrix Rotation
    {
      get { return cameraRotation; }
    }

    public Vector3 FinalTarget
    {
      get { return cameraFinalTarget; }
    }

    const float rotationSpeed = 0.3f;
    const float moveSpeed = 30.0f;
    MouseState originalMouseState;

    public CameraNormal(Game g) : base(g) { }


    private void UpdateViewMatrix()
    {
      cameraRotation = Matrix.CreateRotationX(_updownRot) * Matrix.CreateRotationY(_leftrightRot);

      Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
      Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
      cameraFinalTarget = Position + cameraRotatedTarget;

      Vector3 cameraOriginalUpVector = Vector3.Up;
      Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

      ViewMatrix = Matrix.CreateLookAt(Position, cameraFinalTarget, cameraRotatedUpVector);

    }

    protected override void LoadContent()
    {
      base.LoadContent();
      UpdateViewMatrix();
      Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
      originalMouseState = Mouse.GetState();

    }

    private void ProcessInput(float amount)
    {
      MouseState currentMouseState = Mouse.GetState();
      if (currentMouseState != originalMouseState)
      {
        float xDifference = currentMouseState.X - originalMouseState.X;
        float yDifference = currentMouseState.Y - originalMouseState.Y;
        _leftrightRot -= rotationSpeed * xDifference * amount;
        _updownRot -= rotationSpeed * yDifference * amount;
        Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
        UpdateViewMatrix();
      }

      Vector3 moveVector = new Vector3(0, 0, 0);
      KeyboardState keyState = Keyboard.GetState();
      if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
        moveVector += new Vector3(0, 0, -1);
      if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
        moveVector += new Vector3(0, 0, 1);
      if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
        moveVector += new Vector3(1, 0, 0);
      if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
        moveVector += new Vector3(-1, 0, 0);
      if (keyState.IsKeyDown(Keys.Q))
        moveVector += new Vector3(0, 1, 0);
      if (keyState.IsKeyDown(Keys.Z))
        moveVector += new Vector3(0, -1, 0);
      AddToCameraPosition(moveVector * amount);
    }

    private void AddToCameraPosition(Vector3 vectorToAdd)
    {
      Matrix cameraRotation = Matrix.CreateRotationX(_updownRot) * Matrix.CreateRotationY(_leftrightRot);
      Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
      _position += moveSpeed * rotatedVector;
      UpdateViewMatrix();
    }


    public override void Update(GameTime gameTime)
    {
      float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
      ProcessInput(timeDifference);
      base.Update(gameTime);
    }
  }

}